﻿using System;
using System.Collections.ObjectModel;
using ParserGenerator.Collections;
using ParserGenerator.Extensions;
using ParserGenerator.GrammarConstruction;
using ParserGenerator.Output;
using ParserGenerator.ScannerConstruction;
using ParserGenerator.SyntacticAnalysis;

namespace ParserGenerator.CodeGeneration
{
    [Serializable]
    public sealed class CSharpScannerGenerator : CSharpLexicalActionWriter
    {
        public ScannerAutomatonTable Table { get; }
        public ScannerAutomaton Automaton => Table.Automaton;

        internal CSharpScannerGenerator(GrammarDeclaration grammar, ScannerAutomatonTable table)
            : base(grammar)
        {
            if (grammar == null)
                throw new ArgumentNullException(nameof(grammar));
            if (table == null)
                throw new ArgumentNullException(nameof(table));
            Table = table;
        }

        public void Generate()
        {
            Generate(new IndentedWriter());
        }

        public void Generate(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            GenerateNamespace(writer);
        }

        public void GenerateNamespace(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            string scannerNamespace = Grammar.GetOptionValue("pg.scanner.namespace", "ParserGenerator.LexicalAnalysis");
            writer.Write("namespace ");
            WriteDottedName(scannerNamespace, writer);
            writer.WriteLine();
            writer.OpenCurlyBlock();
            GenerateUsings(writer);
            GenerateScannerClass(writer);
            writer.CloseCurlyBlock();
        }

        public void GenerateUsings(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            Expression v;
            if (Grammar.TryGetOptionValue("pg.scanner.usings", out v))
            {
                Sequence<Expression> symbols;
                if (v.IsTuple)
                    symbols = v.AsTuple.Elements;
                else
                    symbols = v.AsList.Elements;
                foreach (Expression symbol in symbols)
                {
                    writer.Write("using ");
                    WriteExpression(symbol, writer);
                    writer.WriteLine(";");
                }
                writer.WriteLine();
            }
        }

        public void GenerateScannerClass(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            bool isIndented = Grammar.GetOptionValue("pg.indented", true);
            bool isSerializable = Grammar.GetOptionValue("pg.scanner.serializable", true);
            bool isInternal = Grammar.GetOptionValue("pg.scanner.internal", false);
            bool isPublic = Grammar.GetOptionValue("pg.scanner.public", true);
            bool isAbstract = Grammar.GetOptionValue("pg.scanner.abstract", false);
            bool isSealed = Grammar.GetOptionValue("pg.scanner.sealed", true);
            bool isStatic = Grammar.GetOptionValue("pg.scanner.static", false);
            bool isPartial = Grammar.GetOptionValue("pg.scanner.partial", true);
            Identifier scannerClassName = Identifier.New(Grammar.GetOptionValue("pg.scanner.classname", "Tokenizer"));
            if (isSerializable)
            {
                writer.Write("[");
                if (UseFullyQualifiedNamespace)
                    writer.Write("global::System.");
                writer.WriteLine("Serializable]");
            }
            writer.Write(isInternal ? "internal " : isPublic ? "public " : "");
            writer.Write(isAbstract ? "abstract " : isSealed ? "sealed " : isStatic ? "static " : "");
            writer.Write(isPartial ? "partial " : "");
            writer.Write("class ");
            WriteIdentifier(scannerClassName, writer);
            writer.WriteLine();
            writer.OpenCurlyBlock();
            GenerateFields(writer);
            writer.WriteLine();
            GenerateProperties(writer);
            writer.WriteLine();
            GenerateTokenTags(writer);
            writer.WriteLine();
            GenerateConstructors(writer);
            if (isIndented)
            {
                writer.WriteLine();
                GeneratePrescanNextTokenMethod(writer);
                writer.WriteLine();
                GenerateCheckIndentationMethod(writer);
            }
            writer.WriteLine();
            GenerateStartMethods(writer);
            writer.WriteLine();
            GenerateSyntaxErrorMethods(writer);
            writer.WriteLine();
            GenerateArrays(writer);
            writer.WriteLine();
            GenerateMapMethod(writer);
            writer.WriteLine();
            GenerateMoveMethod(writer);
            writer.WriteLine();
            GenerateIsLineTerminatorMethod(writer);
            if (isIndented)
            {
                writer.WriteLine();
                GenerateIsNonLineTerminatorWhiteSpaceMethod(writer);
            }
            writer.WriteLine();
            GenerateScanNextTokenMethod(writer);
            writer.WriteLine();
            GenerateGetNextTokenMethod(writer);
            if (Automaton.Tokens.NamedTokens.ContainsKey("Identifier"))
            {
                writer.WriteLine();
                GenerateMapIdentifierMethod(writer);
                writer.WriteLine();
                GenerateIsKeywordMethod(writer);
            }
            writer.WriteLine();
            GenerateTestMethod(writer);
            writer.CloseCurlyBlock();
        }

        public void GenerateFields(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            bool isIndented = Grammar.GetOptionValue("pg.indented", true);
            Identifier pendinName = Identifier.New(Grammar.GetOptionValue("pg.scanner.pendin.name", "_pendin"));
            Identifier tabIndentName = Identifier.New(Grammar.GetOptionValue("pg.scanner.tabindent.name", "_tabIndent"));
            Identifier spaceIndentName = Identifier.New(Grammar.GetOptionValue("pg.scanner.spaceindent.name", "_spaceIndent"));
            Identifier currentStartName = Identifier.New(Grammar.GetOptionValue("pg.scanner.currentstart.name", "_currentStart"));
            Identifier savedStartsName = Identifier.New(Grammar.GetOptionValue("pg.scanner.savedstarts.name", "_savedStarts"));
            Identifier stackClassName = Identifier.New(Grammar.GetOptionValue("pg.stack.classname", "Stack"));
            string stackNamespace = Grammar.GetOptionValue("pg.stack.namespace", "System.Collections.Generic");
            writer.Write("private int ");
            WriteIdentifier(currentStartName, writer);
            writer.WriteLine(";");
            if (isIndented)
            {
                writer.Write("private int ");
                WriteIdentifier(pendinName, writer);
                writer.WriteLine(";");
                writer.Write("private readonly ");
                if (UseFullyQualifiedNamespace)
                {
                    writer.Write("global::");
                    WriteDottedName(stackNamespace, writer);
                    writer.Write(".");
                }
                WriteIdentifier(stackClassName, writer);
                writer.Write("<int> ");
                WriteIdentifier(tabIndentName, writer);
                writer.Write(" = new ");
                if (UseFullyQualifiedNamespace)
                {
                    writer.Write("global::");
                    WriteDottedName(stackNamespace, writer);
                    writer.Write(".");
                }
                WriteIdentifier(stackClassName, writer);
                writer.WriteLine("<int>();");
                writer.Write("private readonly ");
                if (UseFullyQualifiedNamespace)
                {
                    writer.Write("global::");
                    WriteDottedName(stackNamespace, writer);
                    writer.Write(".");
                }
                WriteIdentifier(stackClassName, writer);
                writer.Write("<int> ");
                WriteIdentifier(spaceIndentName, writer);
                writer.Write(" = new ");
                if (UseFullyQualifiedNamespace)
                {
                    writer.Write("global::");
                    WriteDottedName(stackNamespace, writer);
                    writer.Write(".");
                }
                WriteIdentifier(stackClassName, writer);
                writer.WriteLine("<int>();");
            }
            writer.Write("private readonly ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(stackNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(stackClassName, writer);
            writer.Write("<int> ");
            WriteIdentifier(savedStartsName, writer);
            writer.Write(" = new ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(stackNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(stackClassName, writer);
            writer.WriteLine("<int>();");
        }

        public void GenerateProperties(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            bool isIndented = Grammar.GetOptionValue("pg.indented", true);
            Identifier readerName = Identifier.New(Grammar.GetOptionValue("pg.reader.name", "Reader"));
            Identifier tokenBeginName = Identifier.New(Grammar.GetOptionValue("pg.scanner.tokenbegin.name", "TokenBegin"));
            Identifier tokenEndName = Identifier.New(Grammar.GetOptionValue("pg.scanner.tokenend.name", "TokenEnd"));
            Identifier tokenValueName = Identifier.New(Grammar.GetOptionValue("pg.scanner.tokenvalue.name", "TokenValue"));
            Identifier levelName = Identifier.New(Grammar.GetOptionValue("pg.scanner.level.name", "Level"));
            Identifier sourceReaderClassName = Identifier.New(Grammar.GetOptionValue("pg.sourcereader.classname", "SourceReader"));
            string sourceReaderNamespace = Grammar.GetOptionValue("pg.sourcereader.namespace", "ParserGenerator.Input");
            Identifier sourceLocationClassName = Identifier.New(Grammar.GetOptionValue("pg.sourcelocation.classname", "SourceLocation"));
            string sourceLocationNamespace = Grammar.GetOptionValue("pg.sourcelocation.namespace", "ParserGenerator.Input");
            Identifier tokenContextName = Identifier.New(Grammar.GetOptionValue("pg.scanner.tokencontext.name", "TokenContext"));
            Identifier sourceContextClassName = Identifier.New(Grammar.GetOptionValue("pg.sourcecontext.classname", "SourceContext"));
            string sourceContextNamespace = Grammar.GetOptionValue("pg.sourcecontext.namespace", "ParserGenerator.Input");
            writer.Write("public ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceReaderNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceReaderClassName, writer);
            writer.Write(" ");
            WriteIdentifier(readerName, writer);
            writer.WriteLine(" { get; private set; }");
            writer.Write("public ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceLocationNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceLocationClassName, writer);
            writer.Write(" ");
            WriteIdentifier(tokenBeginName, writer);
            writer.WriteLine(" { get; private set; }");
            writer.Write("public ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceLocationNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceLocationClassName, writer);
            writer.Write(" ");
            WriteIdentifier(tokenEndName, writer);
            writer.WriteLine(" { get; private set; }");
            writer.Write("public ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceContextNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceContextClassName, writer);
            writer.Write(" ");
            WriteIdentifier(tokenContextName, writer);
            writer.WriteLine(" { get; private set; }");
            writer.Write("public object ");
            WriteIdentifier(tokenValueName, writer);
            writer.WriteLine(" { get; private set; }");
            writer.Write("public int ");
            WriteIdentifier(levelName, writer);
            writer.WriteLine(" { get; private set; }");
        }

        public void GenerateTokenTags(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            string tokenTagAccess = Grammar.GetOptionValue("pg.scanner.tokentag.access", "public");
            Identifier tokenTagSuffix = Identifier.New(Grammar.GetOptionValue("pg.scanner.tokentag.suffix", "TokenTag"));
            foreach (GrammarTerminalSymbol sym in Automaton.Tokens.NamedTokens.Values)
            {
                writer.Write(tokenTagAccess);
                if (!string.IsNullOrEmpty(tokenTagAccess))
                    writer.Write(" ");
                writer.Write("const int ");
                string name = sym.Name + tokenTagSuffix.Name;
                WriteIdentifier(name, writer);
                writer.Write(" = ");
                WriteIntegerLiteral(sym.Number, writer);
                writer.WriteLine(";");
            }
        }

        public void GenerateConstructorBody(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            bool isIndented = Grammar.GetOptionValue("pg.indented", true);
            Identifier scannerConstructorReaderName = Identifier.New(Grammar.GetOptionValue("pg.scanner.constructor.reader.name", "reader"));
            Identifier tabIndentName = Identifier.New(Grammar.GetOptionValue("pg.scanner.tabindent.name", "_tabIndent"));
            Identifier spaceIndentName = Identifier.New(Grammar.GetOptionValue("pg.scanner.spaceindent.name", "_spaceIndent"));
            Identifier stackPushName = Identifier.New(Grammar.GetOptionValue("pg.stack.push.name", "Push"));
            Identifier tokenBeginName = Identifier.New(Grammar.GetOptionValue("pg.scanner.tokenbegin.name", "TokenBegin"));
            Identifier tokenEndName = Identifier.New(Grammar.GetOptionValue("pg.scanner.tokenend.name", "TokenEnd"));
            Identifier readerName = Identifier.New(Grammar.GetOptionValue("pg.reader.name", "Reader"));
            Identifier readerLocationName = Identifier.New(Grammar.GetOptionValue("pg.reader.location.name", "Location"));
            writer.Write("if (");
            WriteIdentifier(scannerConstructorReaderName, writer);
            writer.WriteLine(" == null)");
            writer.OpenBlock();
            writer.Write("throw new ");
            if (UseFullyQualifiedNamespace)
                writer.Write("global::System.");
            writer.Write("ArgumentNullException(nameof(");
            WriteIdentifier(scannerConstructorReaderName, writer);
            writer.WriteLine("));");
            writer.CloseBlock();
            WriteIdentifier(readerName, writer);
            writer.Write(" = ");
            WriteIdentifier(scannerConstructorReaderName, writer);
            writer.WriteLine(";");
            WriteIdentifier(tokenBeginName, writer);
            writer.Write(" = ");
            WriteIdentifier(tokenEndName, writer);
            writer.Write(" = ");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.WriteLine(";");
            if (isIndented)
            {
                WriteIdentifier(tabIndentName, writer);
                writer.Write(".");
                WriteIdentifier(stackPushName, writer);
                writer.WriteLine("(0);");
                WriteIdentifier(spaceIndentName, writer);
                writer.Write(".");
                WriteIdentifier(stackPushName, writer);
                writer.WriteLine("(0);");
            }
        }

        public void GenerateConstructors(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            bool isIndented = Grammar.GetOptionValue("pg.indented", true);
            bool isAbstract = Grammar.GetOptionValue("pg.scanner.abstract", false);
            Identifier scannerClassName = Identifier.New(Grammar.GetOptionValue("pg.scanner.classname", "Tokenizer"));
            Identifier scannerConstructorReaderName = Identifier.New(Grammar.GetOptionValue("pg.scanner.constructor.reader.name", "reader"));
            Identifier sourceReaderClassName = Identifier.New(Grammar.GetOptionValue("pg.sourcereader.classname", "SourceReader"));
            Identifier sourceReaderFromConsoleName = Identifier.New(Grammar.GetOptionValue("pg.sourcereader.fromconsole.name", "FromConsole"));
            string sourceReaderNamespace = Grammar.GetOptionValue("pg.sourcereader.namespace", "ParserGenerator.Input");
            writer.Write(isAbstract ? "protected " : "public ");
            WriteIdentifier(scannerClassName, writer);
            writer.WriteLine("()");
            writer.OpenBlock();
            writer.Write(": this(");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceReaderNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceReaderClassName, writer);
            writer.Write(".");
            WriteIdentifier(sourceReaderFromConsoleName, writer);
            writer.WriteLine("())");
            writer.CloseBlock();
            writer.OpenCurlyBlock();
            writer.CloseCurlyBlock();
            writer.WriteLine();
            writer.Write(isAbstract ? "protected " : "public ");
            WriteIdentifier(scannerClassName, writer);
            writer.Write("(");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceReaderNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceReaderClassName, writer);
            writer.Write(" ");
            WriteIdentifier(scannerConstructorReaderName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            GenerateConstructorBody(writer);
            writer.CloseCurlyBlock();
        }

        public void GeneratePrescanNextTokenMethod(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            Identifier prescanNextTokenName = Identifier.New(Grammar.GetOptionValue("pg.scanner.prescannexttoken.name", "PrescanNextToken"));
            Identifier prescanNextTokenCallbackName = Identifier.New(Grammar.GetOptionValue("pg.scanner.prescannexttokencallback.name", "PrescanNextTokenCallback"));
            Identifier isBlankLineName = Identifier.New(Grammar.GetOptionValue("pg.scanner.prescannexttoken.isblankline.name", "isBlankLine"));
            Identifier commentName = Identifier.New(Grammar.GetOptionValue("pg.scanner.prescannexttoken.comment.name", "comment"));
            Identifier lineBeginName = Identifier.New(Grammar.GetOptionValue("pg.scanner.prescannexttoken.linebegin.name", "lineBegin"));
            Identifier lineEndName = Identifier.New(Grammar.GetOptionValue("pg.scanner.prescannexttoken.lineend.name", "lineEnd"));
            Identifier locName = Identifier.New(Grammar.GetOptionValue("pg.scanner.prescannexttoken.loc.name", "loc"));
            Identifier levelName = Identifier.New(Grammar.GetOptionValue("pg.scanner.level.name", "Level"));
            Identifier tokenBeginName = Identifier.New(Grammar.GetOptionValue("pg.scanner.tokenbegin.name", "TokenBegin"));
            Identifier readerName = Identifier.New(Grammar.GetOptionValue("pg.reader.name", "Reader"));
            Identifier readerLocationName = Identifier.New(Grammar.GetOptionValue("pg.reader.location.name", "Location"));
            Identifier readerIsInteractiveName = Identifier.New(Grammar.GetOptionValue("pg.reader.isinteractive.name", "IsInteractive"));
            Identifier locationOffsetName = Identifier.New(Grammar.GetOptionValue("pg.reader.location.offset.name", "Offset"));
            Identifier cName = Identifier.New(Grammar.GetOptionValue("pg.scanner.c.name", "c"));
            Identifier readerPeekName = Identifier.New(Grammar.GetOptionValue("pg.reader.peek.name", "Peek"));
            Identifier readerReadName = Identifier.New(Grammar.GetOptionValue("pg.reader.read.name", "Read"));
            Identifier isNonLineTerminatorWhiteSpaceName = Identifier.New(Grammar.GetOptionValue("pg.scanner.isnonlineterminatorwhitespace.name", "IsNonLineTerminatorWhiteSpace"));
            Identifier isLineTerminatorName = Identifier.New(Grammar.GetOptionValue("pg.scanner.islineterminator.name", "IsLineTerminator"));
            Identifier checkIndentationName = Identifier.New(Grammar.GetOptionValue("pg.scanner.checkindentation.name", "CheckIndentation"));
            Identifier pendinName = Identifier.New(Grammar.GetOptionValue("pg.scanner.pendin.name", "_pendin"));
            Identifier sourceLocationClassName = Identifier.New(Grammar.GetOptionValue("pg.sourcelocation.classname", "SourceLocation"));
            string sourceLocationNamespace = Grammar.GetOptionValue("pg.sourcelocation.namespace", "ParserGenerator.Input");
            Identifier scannerSyntaxErrorName = Identifier.New(Grammar.GetOptionValue("pg.scanner.syntaxerror.name", "SyntaxError"));
            Identifier invalidLineContinuationResourceName = Identifier.New(Grammar.GetOptionValue("pg.sr.InvalidLineContinuation.name", "InvalidLineContinuation"));
            string invalidLineContinuationResourceText = Grammar.GetOptionValue("pg.sr.InvalidLineContinuation.text", "Unexpected character after line continuation character.");
            string endMarkerName = Grammar.GetOptionValue("pg.endmarker.name", "None");
            string indentName = Grammar.GetOptionValue("pg.indent.name", "Indent");
            string unindentName = Grammar.GetOptionValue("pg.unindent.name", "Unindent");
            string newLineName = Grammar.GetOptionValue("pg.newline.name", "NewLine");
            GrammarTerminalSymbol endMarker = Automaton.Tokens.LookupNamed(endMarkerName);
            GrammarTerminalSymbol indent = Automaton.Tokens.LookupNamed(indentName);
            GrammarTerminalSymbol unindent = Automaton.Tokens.LookupNamed(unindentName);
            GrammarTerminalSymbol newLine = Automaton.Tokens.LookupNamed(newLineName);
            writer.Write("private int ");
            WriteIdentifier(prescanNextTokenName, writer);
            writer.WriteLine("()");
            writer.OpenCurlyBlock();
            writer.WriteLine("while (true)");
            writer.OpenCurlyBlock();
            writer.Write("bool ");
            WriteIdentifier(isBlankLineName, writer);
            writer.WriteLine(" = false;");
            writer.Write("if (");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.Write(".");
            WriteIdentifier(locationOffsetName, writer);
            writer.WriteLine(" == 0)");
            writer.OpenCurlyBlock();
            writer.Write("while (");
            WriteIdentifier(isNonLineTerminatorWhiteSpaceName, writer);
            writer.Write("(");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerPeekName, writer);
            writer.WriteLine("()))");
            writer.OpenBlock();
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerReadName, writer);
            writer.WriteLine("();");
            writer.CloseBlock();
            writer.Write("bool ");
            WriteIdentifier(commentName, writer);
            writer.Write(" = ");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerPeekName, writer);
            writer.WriteLine("() == '#';");
            writer.Write("bool ");
            WriteIdentifier(lineBeginName, writer);
            writer.Write(" = ");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.Write(".");
            WriteIdentifier(locationOffsetName, writer);
            writer.WriteLine(" == 0;");
            writer.Write("bool ");
            WriteIdentifier(lineEndName, writer);
            writer.Write(" = ");
            WriteIdentifier(isLineTerminatorName, writer);
            writer.Write("(");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerPeekName, writer);
            writer.WriteLine("());");
            WriteIdentifier(isBlankLineName, writer);
            writer.Write(" = ");
            WriteIdentifier(commentName, writer);
            writer.Write(" || ");
            WriteIdentifier(lineEndName, writer);
            writer.Write(" && !(");
            WriteIdentifier(lineBeginName, writer);
            writer.Write(" && ");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerIsInteractiveName, writer);
            writer.WriteLine(");");
            writer.Write("if (!");
            WriteIdentifier(isBlankLineName, writer);
            writer.Write(" && ");
            WriteIdentifier(levelName, writer);
            writer.WriteLine(" == 0)");
            writer.OpenBlock();
            WriteIdentifier(checkIndentationName, writer);
            writer.WriteLine("();");
            writer.CloseBlock();
            writer.CloseCurlyBlock();
            writer.Write("if (");
            WriteIdentifier(pendinName, writer);
            writer.WriteLine(" != 0)");
            writer.OpenCurlyBlock();
            WriteIdentifier(tokenBeginName, writer);
            writer.Write(" = ");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.WriteLine(";");
            writer.Write("if (");
            WriteIdentifier(pendinName, writer);
            writer.WriteLine(" < 0)");
            writer.OpenCurlyBlock();
            WriteIdentifier(pendinName, writer);
            writer.WriteLine("++;");
            writer.WriteLine("return {0};", unindent.Number);
            writer.CloseCurlyBlock();
            writer.WriteLine("else");
            writer.OpenCurlyBlock();
            WriteIdentifier(pendinName, writer);
            writer.WriteLine("--;");
            writer.WriteLine("return {0};", indent.Number);
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
            writer.WriteLine("while (true)");
            writer.OpenCurlyBlock();
            writer.Write("while (");
            WriteIdentifier(isNonLineTerminatorWhiteSpaceName, writer);
            writer.Write("(");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerPeekName, writer);
            writer.WriteLine("()))");
            writer.OpenBlock();
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerReadName, writer);
            writer.WriteLine("();");
            writer.CloseBlock();
            writer.Write("if (");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerPeekName, writer);
            writer.WriteLine("() == '#')");
            writer.OpenCurlyBlock();
            writer.Write("int ");
            WriteIdentifier(cName, writer);
            writer.WriteLine(";");
            writer.Write("while ((");
            WriteIdentifier(cName, writer);
            writer.Write(" = ");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerPeekName, writer);
            writer.WriteLine("()) != -1)");
            writer.OpenBlock();
            writer.Write("if (");
            WriteIdentifier(isLineTerminatorName, writer);
            writer.Write("(");
            WriteIdentifier(cName, writer);
            writer.WriteLine("))");
            writer.OpenBlock();
            writer.WriteLine("break;");
            writer.CloseBlock();
            writer.WriteLine("else");
            writer.OpenBlock();
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerReadName, writer);
            writer.WriteLine("();");
            writer.CloseBlock();
            writer.CloseBlock();
            writer.CloseCurlyBlock();
            writer.Write("if (");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerPeekName, writer);
            writer.WriteLine("() == -1)");
            writer.OpenCurlyBlock();
            WriteIdentifier(tokenBeginName, writer);
            writer.Write(" = ");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.WriteLine(";");
            writer.WriteLine("return {0};", endMarker.Number);
            writer.CloseCurlyBlock();
            writer.Write("if (");
            WriteIdentifier(isLineTerminatorName, writer);
            writer.Write("(");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerPeekName, writer);
            writer.WriteLine("()))");
            writer.OpenCurlyBlock();
            writer.Write("if (");
            WriteIdentifier(isBlankLineName, writer);
            writer.Write(" || ");
            WriteIdentifier(levelName, writer);
            writer.WriteLine(" > 0)");
            writer.OpenCurlyBlock();
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerReadName, writer);
            writer.WriteLine("();");
            writer.WriteLine("break;");
            writer.CloseCurlyBlock();
            writer.WriteLine("else");
            writer.OpenCurlyBlock();
            WriteIdentifier(tokenBeginName, writer);
            writer.Write(" = ");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.WriteLine(";");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerReadName, writer);
            writer.WriteLine("();");
            writer.WriteLine("return {0};", newLine.Number);
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
            writer.Write("if (");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerPeekName, writer);
            writer.WriteLine("() == '\\\\')");
            writer.OpenCurlyBlock();
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceLocationNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceLocationClassName, writer);
            writer.Write(" ");
            WriteIdentifier(locName, writer);
            writer.Write(" = ");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.WriteLine(";");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerReadName, writer);
            writer.WriteLine("();");
            writer.Write("if (");
            WriteIdentifier(isLineTerminatorName, writer);
            writer.Write("(");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerPeekName, writer);
            writer.WriteLine("()))");
            writer.OpenCurlyBlock();
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerReadName, writer);
            writer.WriteLine("();");
            writer.WriteLine("continue;");
            writer.CloseCurlyBlock();
            writer.WriteLine("else");
            writer.OpenCurlyBlock();
            writer.Write("throw ");
            WriteIdentifier(scannerSyntaxErrorName, writer);
            writer.Write("(");
            if (UseStringResources)
            {
                if (UseFullyQualifiedNamespace)
                {
                    writer.Write("global::");
                    WriteDottedName(StringResourcesNamespace, writer);
                    writer.Write(".");
                }
                WriteIdentifier(StringResourcesClassName, writer);
                writer.Write(".");
                WriteIdentifier(invalidLineContinuationResourceName, writer);
            }
            else
            {
                WriteStringLiteral(invalidLineContinuationResourceText, writer);
            }
            writer.Write(", ");
            WriteIdentifier(locName, writer);
            writer.WriteLine(");");
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
            writer.Write("return ");
            WriteIdentifier(prescanNextTokenCallbackName, writer);
            writer.WriteLine("();");
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
        }

        public void GenerateCheckIndentationMethod(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            Identifier checkIndentationName = Identifier.New(Grammar.GetOptionValue("pg.scanner.checkindentation.name", "CheckIndentation"));
            Identifier eName = Identifier.New(Grammar.GetOptionValue("pg.scanner.checkindentation.e.name", "e"));
            Identifier readerName = Identifier.New(Grammar.GetOptionValue("pg.reader.name", "Reader"));
            Identifier readerLocationName = Identifier.New(Grammar.GetOptionValue("pg.reader.location.name", "Location"));
            Identifier locationOffsetName = Identifier.New(Grammar.GetOptionValue("pg.reader.location.offset.name", "Offset"));
            Identifier locationColumnName = Identifier.New(Grammar.GetOptionValue("pg.reader.location.column.name", "Column"));
            Identifier pendinName = Identifier.New(Grammar.GetOptionValue("pg.scanner.pendin.name", "_pendin"));
            Identifier tabIndentName = Identifier.New(Grammar.GetOptionValue("pg.scanner.tabindent.name", "_tabIndent"));
            Identifier spaceIndentName = Identifier.New(Grammar.GetOptionValue("pg.scanner.spaceindent.name", "_spaceIndent"));
            Identifier stackPushName = Identifier.New(Grammar.GetOptionValue("pg.stack.push.name", "Push"));
            Identifier stackPopName = Identifier.New(Grammar.GetOptionValue("pg.stack.pop.name", "Pop"));
            Identifier stackPeekName = Identifier.New(Grammar.GetOptionValue("pg.stack.peek.name", "Peek"));
            Identifier stackCountName = Identifier.New(Grammar.GetOptionValue("pg.stack.count.name", "Count"));
            Identifier tabErrorExceptionClassName = Identifier.New(Grammar.GetOptionValue("pg.taberrorexception.classname", "TabErrorException"));
            string tabErrorExceptionNamespace = Grammar.GetOptionValue("pg.taberrorexception.namespace", "ParserGenerator.Exceptions");
            Identifier tabErrorExceptionAddLocationName = Identifier.New(Grammar.GetOptionValue("pg.taberrorexception.addlocation.name", "AddLocation"));
            Identifier indentationErrorExceptionClassName = Identifier.New(Grammar.GetOptionValue("pg.indentationerrorexception.classname", "IndentationErrorException"));
            string indentationErrorExceptionNamespace = Grammar.GetOptionValue("pg.indentationerrorexception.namespace", "ParserGenerator.Exceptions");
            Identifier indentationErrorExceptionAddLocationName = Identifier.New(Grammar.GetOptionValue("pg.indentationerrorexception.addlocation.name", "AddLocation"));
            Identifier inconsistentTabSpaceResourceName = Identifier.New(Grammar.GetOptionValue("pg.sr.InconsistentTabSpace.name", "InconsistentTabSpace"));
            string InconsistentTabSpaceResourceText = Grammar.GetOptionValue("pg.sr.InconsistentTabSpace.text", "Inconsistent use of tabs and spaces in indenation.");
            Identifier mismatchUnindentResourceName = Identifier.New(Grammar.GetOptionValue("pg.sr.MismatchUnindent.name", "MismatchUnindent"));
            string mismatchUnindentResourceText = Grammar.GetOptionValue("pg.sr.MismatchUnindent.text", "Unindent does not match any outer indentation level.");
            writer.Write("private void ");
            WriteIdentifier(checkIndentationName, writer);
            writer.WriteLine("()");
            writer.OpenCurlyBlock();
            writer.Write("if (");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.Write(".");
            WriteIdentifier(locationOffsetName, writer);
            writer.Write(" > ");
            WriteIdentifier(tabIndentName, writer);
            writer.Write(".");
            WriteIdentifier(stackPeekName, writer);
            writer.WriteLine("())");
            writer.OpenCurlyBlock();
            writer.Write("if (");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.Write(".");
            WriteIdentifier(locationColumnName, writer);
            writer.Write(" > ");
            WriteIdentifier(spaceIndentName, writer);
            writer.Write(".");
            WriteIdentifier(stackPeekName, writer);
            writer.WriteLine("())");
            writer.OpenCurlyBlock();
            WriteIdentifier(pendinName, writer);
            writer.WriteLine("++;");
            WriteIdentifier(tabIndentName, writer);
            writer.Write(".");
            WriteIdentifier(stackPushName, writer);
            writer.Write("(");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.Write(".");
            WriteIdentifier(locationOffsetName, writer);
            writer.WriteLine(");");
            WriteIdentifier(spaceIndentName, writer);
            writer.Write(".");
            WriteIdentifier(stackPushName, writer);
            writer.Write("(");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.Write(".");
            WriteIdentifier(locationColumnName, writer);
            writer.WriteLine(");");
            writer.CloseCurlyBlock();
            writer.WriteLine("else");
            writer.OpenCurlyBlock();
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(tabErrorExceptionNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(tabErrorExceptionClassName, writer);
            writer.Write(" ");
            WriteIdentifier(eName, writer);
            writer.Write(" = new ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(tabErrorExceptionNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(tabErrorExceptionClassName, writer);
            writer.Write("(");
            if (UseStringResources)
            {
                if (UseFullyQualifiedNamespace)
                {
                    writer.Write("global::");
                    WriteDottedName(StringResourcesNamespace, writer);
                    writer.Write(".");
                }
                WriteIdentifier(StringResourcesClassName, writer);
                writer.Write(".");
                WriteIdentifier(inconsistentTabSpaceResourceName, writer);
            }
            else
            {
                WriteStringLiteral(InconsistentTabSpaceResourceText, writer);
            }
            writer.WriteLine(");");
            WriteIdentifier(eName, writer);
            writer.Write(".");
            WriteIdentifier(tabErrorExceptionAddLocationName, writer);
            writer.Write("(");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.WriteLine(");");
            writer.Write("throw ");
            WriteIdentifier(eName, writer);
            writer.WriteLine(";");
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
            writer.Write("else if (");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.Write(".");
            WriteIdentifier(locationOffsetName, writer);
            writer.Write(" < ");
            WriteIdentifier(tabIndentName, writer);
            writer.Write(".");
            WriteIdentifier(stackPeekName, writer);
            writer.WriteLine("())");
            writer.OpenCurlyBlock();
            writer.Write("while (");
            WriteIdentifier(tabIndentName, writer);
            writer.Write(".");
            WriteIdentifier(stackCountName, writer);
            writer.Write(" > 1 && ");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.Write(".");
            WriteIdentifier(locationOffsetName, writer);
            writer.Write(" < ");
            WriteIdentifier(tabIndentName, writer);
            writer.Write(".");
            WriteIdentifier(stackPeekName, writer);
            writer.WriteLine("())");
            writer.OpenCurlyBlock();
            WriteIdentifier(pendinName, writer);
            writer.WriteLine("--;");
            WriteIdentifier(tabIndentName, writer);
            writer.Write(".");
            WriteIdentifier(stackPopName, writer);
            writer.WriteLine("();");
            WriteIdentifier(spaceIndentName, writer);
            writer.Write(".");
            WriteIdentifier(stackPopName, writer);
            writer.WriteLine("();");
            writer.CloseCurlyBlock();
            writer.Write("if (");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.Write(".");
            WriteIdentifier(locationOffsetName, writer);
            writer.Write(" != ");
            WriteIdentifier(tabIndentName, writer);
            writer.Write(".");
            WriteIdentifier(stackPeekName, writer);
            writer.WriteLine("())");
            writer.OpenCurlyBlock();
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(indentationErrorExceptionNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(indentationErrorExceptionClassName, writer);
            writer.Write(" ");
            WriteIdentifier(eName, writer);
            writer.Write(" = new ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(indentationErrorExceptionNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(indentationErrorExceptionClassName, writer);
            writer.Write("(");
            if (UseStringResources)
            {
                if (UseFullyQualifiedNamespace)
                {
                    writer.Write("global::");
                    WriteDottedName(StringResourcesNamespace, writer);
                    writer.Write(".");
                }
                WriteIdentifier(StringResourcesClassName, writer);
                writer.Write(".");
                WriteIdentifier(mismatchUnindentResourceName, writer);
            }
            else
            {
                WriteStringLiteral(mismatchUnindentResourceText, writer);
            }
            writer.WriteLine(");");
            WriteIdentifier(eName, writer);
            writer.Write(".");
            WriteIdentifier(indentationErrorExceptionAddLocationName, writer);
            writer.Write("(");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.WriteLine(");");
            writer.Write("throw ");
            WriteIdentifier(eName, writer);
            writer.WriteLine(";");
            writer.CloseCurlyBlock();
            writer.Write("if (");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.Write(".");
            WriteIdentifier(locationColumnName, writer);
            writer.Write(" != ");
            WriteIdentifier(spaceIndentName, writer);
            writer.Write(".");
            WriteIdentifier(stackPeekName, writer);
            writer.WriteLine("())");
            writer.OpenCurlyBlock();
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(tabErrorExceptionNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(tabErrorExceptionClassName, writer);
            writer.Write(" ");
            WriteIdentifier(eName, writer);
            writer.Write(" = new ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(tabErrorExceptionNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(tabErrorExceptionClassName, writer);
            writer.Write("(");
            if (UseStringResources)
            {
                if (UseFullyQualifiedNamespace)
                {
                    writer.Write("global::");
                    WriteDottedName(StringResourcesNamespace, writer);
                    writer.Write(".");
                }
                WriteIdentifier(StringResourcesClassName, writer);
                writer.Write(".");
                WriteIdentifier(inconsistentTabSpaceResourceName, writer);
            }
            else
            {
                WriteStringLiteral(InconsistentTabSpaceResourceText, writer);
            }
            writer.WriteLine(");");
            WriteIdentifier(eName, writer);
            writer.Write(".");
            WriteIdentifier(tabErrorExceptionAddLocationName, writer);
            writer.Write("(");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.WriteLine(");");
            writer.Write("throw ");
            WriteIdentifier(eName, writer);
            writer.WriteLine(";");
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
            writer.WriteLine("else");
            writer.OpenCurlyBlock();
            writer.Write("if (");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.Write(".");
            WriteIdentifier(locationColumnName, writer);
            writer.Write(" != ");
            WriteIdentifier(spaceIndentName, writer);
            writer.Write(".");
            WriteIdentifier(stackPeekName, writer);
            writer.WriteLine("())");
            writer.OpenCurlyBlock();
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(tabErrorExceptionNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(tabErrorExceptionClassName, writer);
            writer.Write(" ");
            WriteIdentifier(eName, writer);
            writer.Write(" = new ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(tabErrorExceptionNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(tabErrorExceptionClassName, writer);
            writer.Write("(");
            if (UseStringResources)
            {
                if (UseFullyQualifiedNamespace)
                {
                    writer.Write("global::");
                    WriteDottedName(StringResourcesNamespace, writer);
                    writer.Write(".");
                }
                WriteIdentifier(StringResourcesClassName, writer);
                writer.Write(".");
                WriteIdentifier(inconsistentTabSpaceResourceName, writer);
            }
            else
            {
                WriteStringLiteral(InconsistentTabSpaceResourceText, writer);
            }
            writer.WriteLine(");");
            WriteIdentifier(eName, writer);
            writer.Write(".");
            WriteIdentifier(tabErrorExceptionAddLocationName, writer);
            writer.Write("(");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.WriteLine(");");
            writer.Write("throw ");
            WriteIdentifier(eName, writer);
            writer.WriteLine(";");
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
        }

        public void GenerateStartMethods(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            Identifier startName = Identifier.New(Grammar.GetOptionValue("pg.scanner.start.name", "start"));
            Identifier currentStartName = Identifier.New(Grammar.GetOptionValue("pg.scanner.currentstart.name", "_currentStart"));
            Identifier savedStartsName = Identifier.New(Grammar.GetOptionValue("pg.scanner.savedstarts.name", "_savedStarts"));
            Identifier stackPushName = Identifier.New(Grammar.GetOptionValue("pg.stack.push.name", "Push"));
            Identifier stackPopName = Identifier.New(Grammar.GetOptionValue("pg.stack.pop.name", "Pop"));
            Identifier pushStartName = Identifier.New(Grammar.GetOptionValue("pg.scanner.pushstart.name", "PushStart"));
            Identifier gotoStartName = Identifier.New(Grammar.GetOptionValue("pg.scanner.gotostart.name", "GotoStart"));
            Identifier popStartName = Identifier.New(Grammar.GetOptionValue("pg.scanner.popstart.name", "PopStart"));
            writer.Write("private void ");
            WriteIdentifier(pushStartName, writer);
            writer.Write("(int ");
            WriteIdentifier(startName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            WriteIdentifier(savedStartsName, writer);
            writer.Write(".");
            WriteIdentifier(stackPushName, writer);
            writer.Write("(");
            WriteIdentifier(currentStartName, writer);
            writer.WriteLine(");");
            WriteIdentifier(currentStartName, writer);
            writer.Write(" = ");
            WriteIdentifier(startName, writer);
            writer.WriteLine(";");
            writer.CloseCurlyBlock();
            writer.WriteLine();
            writer.Write("private void ");
            WriteIdentifier(gotoStartName, writer);
            writer.Write("(int ");
            WriteIdentifier(startName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            WriteIdentifier(currentStartName, writer);
            writer.Write(" = ");
            WriteIdentifier(startName, writer);
            writer.WriteLine(";");
            writer.CloseCurlyBlock();
            writer.WriteLine();
            writer.Write("private void ");
            WriteIdentifier(popStartName, writer);
            writer.WriteLine("()");
            writer.OpenCurlyBlock();
            WriteIdentifier(currentStartName, writer);
            writer.Write(" = ");
            WriteIdentifier(savedStartsName, writer);
            writer.Write(".");
            WriteIdentifier(stackPopName, writer);
            writer.WriteLine("();");
            writer.CloseCurlyBlock();
        }

        public void GenerateSyntaxErrorMethods(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            Identifier syntaxErrorExceptionClassName = Identifier.New(Grammar.GetOptionValue("pg.syntaxerrorexception.classname", "SyntaxErrorException"));
            Identifier syntaxErrorExceptionAddLocationName = Identifier.New(Grammar.GetOptionValue("pg.syntaxerrorexception.addlocation.name", "AddLocation"));
            string syntaxErrorExceptionNamespace = Grammar.GetOptionValue("pg.syntaxerrorexception.namespace", "ParserGenerator.Exceptions");
            Identifier sourceLocationClassName = Identifier.New(Grammar.GetOptionValue("pg.sourcelocation.classname", "SourceLocation"));
            Identifier sourceLocationBeginName = Identifier.New(Grammar.GetOptionValue("pg.sourcelocation.begin.name", "Begin"));
            string sourceLocationNamespace = Grammar.GetOptionValue("pg.sourcelocation.namespace", "ParserGenerator.Input");
            Identifier sourceContextClassName = Identifier.New(Grammar.GetOptionValue("pg.sourcecontext.classname", "SourceContext"));
            string sourceContextNamespace = Grammar.GetOptionValue("pg.sourcecontext.namespace", "ParserGenerator.Input");
            Identifier scannerSyntaxErrorName = Identifier.New(Grammar.GetOptionValue("pg.scanner.syntaxerror.name", "SyntaxError"));
            Identifier eName = Identifier.New(Grammar.GetOptionValue("pg.scanner.syntaxerror.e.name", "e"));
            Identifier messageName = Identifier.New(Grammar.GetOptionValue("pg.scanner.syntaxerror.message.name", "message"));
            Identifier locationName = Identifier.New(Grammar.GetOptionValue("pg.scanner.syntaxerror.location.name", "location"));
            Identifier contextName = Identifier.New(Grammar.GetOptionValue("pg.scanner.syntaxerror.context.name", "context"));
            Identifier readerName = Identifier.New(Grammar.GetOptionValue("pg.reader.name", "Reader"));
            Identifier readerLocationName = Identifier.New(Grammar.GetOptionValue("pg.reader.location.name", "Location"));
            writer.Write("public ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(syntaxErrorExceptionNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(syntaxErrorExceptionClassName, writer);
            writer.Write(" ");
            WriteIdentifier(scannerSyntaxErrorName, writer);
            writer.Write("(string ");
            WriteIdentifier(messageName, writer);
            writer.Write(", ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceLocationNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceLocationClassName, writer);
            writer.Write(" ");
            WriteIdentifier(locationName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            writer.Write("if (");
            WriteIdentifier(messageName, writer);
            writer.WriteLine(" == null)");
            writer.OpenBlock();
            writer.Write("throw new ");
            if (UseFullyQualifiedNamespace)
                writer.Write("global::System.");
            writer.Write("ArgumentNullException(nameof(");
            WriteIdentifier(messageName, writer);
            writer.WriteLine("));");
            writer.CloseBlock();
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(syntaxErrorExceptionNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(syntaxErrorExceptionClassName, writer);
            writer.Write(" ");
            WriteIdentifier(eName, writer);
            writer.Write(" = new ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(syntaxErrorExceptionNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(syntaxErrorExceptionClassName, writer);
            writer.Write("(");
            WriteIdentifier(messageName, writer);
            writer.WriteLine(");");
            WriteIdentifier(eName, writer);
            writer.Write(".");
            WriteIdentifier(syntaxErrorExceptionAddLocationName, writer);
            writer.Write("(");
            WriteIdentifier(locationName, writer);
            writer.WriteLine(");");
            writer.Write("return ");
            WriteIdentifier(eName, writer);
            writer.WriteLine(";");
            writer.CloseCurlyBlock();
            writer.WriteLine();
            writer.Write("public ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(syntaxErrorExceptionNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(syntaxErrorExceptionClassName, writer);
            writer.Write(" ");
            WriteIdentifier(scannerSyntaxErrorName, writer);
            writer.Write("(string ");
            WriteIdentifier(messageName, writer);
            writer.Write(", ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceContextNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceContextClassName, writer);
            writer.Write(" ");
            WriteIdentifier(contextName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            writer.Write("if (");
            WriteIdentifier(messageName, writer);
            writer.WriteLine(" == null)");
            writer.OpenBlock();
            writer.Write("throw new ");
            if (UseFullyQualifiedNamespace)
                writer.Write("global::System.");
            writer.Write("ArgumentNullException(nameof(");
            WriteIdentifier(messageName, writer);
            writer.WriteLine("));");
            writer.CloseBlock();
            writer.Write("if (");
            WriteIdentifier(contextName, writer);
            writer.WriteLine(" == null)");
            writer.OpenBlock();
            writer.Write("throw new ");
            if (UseFullyQualifiedNamespace)
                writer.Write("global::System.");
            writer.Write("ArgumentNullException(nameof(");
            WriteIdentifier(contextName, writer);
            writer.WriteLine("));");
            writer.CloseBlock();
            writer.Write("return ");
            WriteIdentifier(scannerSyntaxErrorName, writer);
            writer.Write("(");
            WriteIdentifier(messageName, writer);
            writer.Write(", ");
            WriteIdentifier(contextName, writer);
            writer.Write(".");
            WriteIdentifier(sourceLocationBeginName, writer);
            writer.WriteLine(");");
            writer.CloseCurlyBlock();
            writer.WriteLine();
            writer.Write("public ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(syntaxErrorExceptionNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(syntaxErrorExceptionClassName, writer);
            writer.Write(" ");
            WriteIdentifier(scannerSyntaxErrorName, writer);
            writer.Write("(string ");
            WriteIdentifier(messageName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            writer.Write("if (");
            WriteIdentifier(messageName, writer);
            writer.WriteLine(" == null)");
            writer.OpenBlock();
            writer.Write("throw new ");
            if (UseFullyQualifiedNamespace)
                writer.Write("global::System.");
            writer.Write("ArgumentNullException(nameof(");
            WriteIdentifier(messageName, writer);
            writer.WriteLine("));");
            writer.CloseBlock();
            writer.Write("return ");
            WriteIdentifier(scannerSyntaxErrorName, writer);
            writer.Write("(");
            WriteIdentifier(messageName, writer);
            writer.Write(", ");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.WriteLine(");");
            writer.CloseCurlyBlock();
        }

        public void GenerateArrays(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            GenerateStringArray("pg.scanner.array.starts", "_starts", Table.StartNameArray, writer);
            GenerateStringArray("pg.scanner.array.tokens", "_tokens", Table.TokenNameArray, writer);
            GenerateIntegerArray("pg.scanner.array.start", "_start", Table.StartArray, writer);
            GenerateIntegerArray("pg.scanner.array.flag", "_flag", Table.FlagArray, writer);
            GenerateIntegerArray("pg.scanner.array.fast", "_fast", Automaton.CharMap.FastArray, writer);
            GenerateIntegerArray("pg.scanner.array.first", "_first", Automaton.CharMap.FirstArray, writer);
            GenerateIntegerArray("pg.scanner.array.last", "_last", Automaton.CharMap.LastArray, writer);
            GenerateIntegerArray("pg.scanner.array.map", "_map", Automaton.CharMap.MapArray, writer);
            GenerateIntegerArray("pg.scanner.array.base", "_sbase", Table.ActionTable.BaseArray, writer);
            GenerateIntegerArray("pg.scanner.array.check", "_scheck", Table.ActionTable.CheckArray, writer);
            GenerateIntegerArray("pg.scanner.array.value", "_svalue", Table.ActionTable.ValueArray, writer, true);
        }

        public void GenerateStringArray(string key, string defaultName, ReadOnlyCollection<string> values, IndentedWriter writer)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));
            if (defaultName == null)
                throw new ArgumentNullException(nameof(defaultName));
            if (values == null)
                throw new ArgumentNullException(nameof(values));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            GenerateStringArray(key, defaultName, values, writer, false);
        }

        public void GenerateStringArray(string key, string defaultName, ReadOnlyCollection<string> values, IndentedWriter writer, bool isLast)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));
            if (defaultName == null)
                throw new ArgumentNullException(nameof(defaultName));
            if (values == null)
                throw new ArgumentNullException(nameof(values));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            string access = Grammar.GetOptionValue(key + ".access", "private");
            Identifier name = Identifier.New(Grammar.GetOptionValue(key + ".name", defaultName));
            writer.Write(access);
            if (!access.IsNullOrEmpty())
                writer.Write(" ");
            writer.Write("static readonly string[] ");
            WriteIdentifier(name, writer);
            writer.WriteLine(" =");
            writer.OpenCurlyBlock();
            WriteStringArrayElements(values, writer);
            writer.CloseBlock("};");
            if (!isLast)
                writer.WriteLine();
        }

        public void GenerateIntegerArray(string key, string defaultName, ReadOnlyCollection<int> values, IndentedWriter writer)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));
            if (defaultName == null)
                throw new ArgumentNullException(nameof(defaultName));
            if (values == null)
                throw new ArgumentNullException(nameof(values));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            GenerateIntegerArray(key, defaultName, values, writer, false);
        }

        public void GenerateIntegerArray(string key, string defaultName, ReadOnlyCollection<int> values, IndentedWriter writer, bool isLast)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));
            if (defaultName == null)
                throw new ArgumentNullException(nameof(defaultName));
            if (values == null)
                throw new ArgumentNullException(nameof(values));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            string access = Grammar.GetOptionValue(key + ".access", "private");
            Identifier name = Identifier.New(Grammar.GetOptionValue(key + ".name", defaultName));
            writer.Write(access);
            if (!access.IsNullOrEmpty())
                writer.Write(" ");
            writer.Write("static readonly int[] ");
            WriteIdentifier(name, writer);
            writer.WriteLine(" =");
            writer.OpenCurlyBlock();
            WriteIntegerArrayElements(values, writer);
            writer.CloseBlock("};");
            if (!isLast)
                writer.WriteLine();
        }

        public void GenerateScanWhileLoop(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            writer.WriteLine("while (true)");
            writer.OpenCurlyBlock();
            Identifier tokenBeginName = Identifier.New(Grammar.GetOptionValue("pg.scanner.tokenbegin.name", "TokenBegin"));
            Identifier tokenEndName = Identifier.New(Grammar.GetOptionValue("pg.scanner.tokenend.name", "TokenEnd"));
            Identifier readerName = Identifier.New(Grammar.GetOptionValue("pg.reader.name", "Reader"));
            Identifier readerLocationName = Identifier.New(Grammar.GetOptionValue("pg.reader.location.name", "Location"));
            Identifier readerPeekName = Identifier.New(Grammar.GetOptionValue("pg.reader.peek.name", "Peek"));
            Identifier startArrayName = Identifier.New(Grammar.GetOptionValue("pg.scanner.array.start.name", "_start"));
            Identifier currentStartName = Identifier.New(Grammar.GetOptionValue("pg.scanner.currentstart.name", "_currentStart"));
            WriteIdentifier(tokenBeginName, writer);
            writer.Write(" = ");
            WriteIdentifier(tokenEndName, writer);
            writer.Write(" = ");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.WriteLine(";");
            bool isIndented = Grammar.GetOptionValue("pg.indented", true);
            if (!isIndented)
            {
                writer.Write("if (");
                WriteIdentifier(startArrayName, writer);
                writer.Write("[");
                WriteIdentifier(currentStartName, writer);
                writer.Write(" * 3 + 2] == 0 && ");
                WriteIdentifier(readerName, writer);
                writer.Write(".");
                WriteIdentifier(readerPeekName, writer);
                writer.WriteLine("() == -1)");
                writer.OpenBlock();
                GrammarTerminalSymbol endMarker = Automaton.Tokens.EndMarkers[0];
                writer.WriteLine("return {0};", endMarker.Number);
                writer.CloseBlock();
            }
            Identifier savedName = Identifier.New(Grammar.GetOptionValue("pg.scanner.saved.name", "saved"));
            Identifier startName = Identifier.New(Grammar.GetOptionValue("pg.scanner.start.name", "start"));
            writer.Write("int ");
            WriteIdentifier(savedName, writer);
            writer.WriteLine(" = -1;");
            writer.Write("int ");
            WriteIdentifier(startName, writer);
            writer.Write(" = ");
            WriteIdentifier(currentStartName, writer);
            writer.WriteLine(";");
            Identifier stateName = Identifier.New(Grammar.GetOptionValue("pg.scanner.state.name", "state"));
            Identifier locationOffsetName = Identifier.New(Grammar.GetOptionValue("pg.reader.location.offset.name", "Offset"));
            writer.Write("int ");
            WriteIdentifier(stateName, writer);
            writer.Write(" = ");
            WriteIdentifier(startArrayName, writer);
            writer.Write("[");
            WriteIdentifier(startName, writer);
            writer.Write(" * 3 + (");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.Write(".");
            WriteIdentifier(locationOffsetName, writer);
            writer.WriteLine(" == 0 ? 0 : 1)];");
            writer.WriteLine("while (true)");
            writer.OpenCurlyBlock();
            Identifier cName = Identifier.New(Grammar.GetOptionValue("pg.scanner.c.name", "c"));
            Identifier readerReadName = Identifier.New(Grammar.GetOptionValue("pg.reader.read.name", "Read"));
            writer.Write("int ");
            WriteIdentifier(cName, writer);
            writer.Write(" = ");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerReadName, writer);
            writer.WriteLine("();");
            writer.Write("if (");
            WriteIdentifier(cName, writer);
            writer.WriteLine(" == -1)");
            writer.OpenBlock();
            writer.WriteLine("break;");
            writer.CloseBlock();
            Identifier resultName = Identifier.New(Grammar.GetOptionValue("pg.scanner.result.name", "result"));
            Identifier moveName = Identifier.New(Grammar.GetOptionValue("pg.scanner.move.name", "Move"));
            writer.Write("int ");
            WriteIdentifier(resultName, writer);
            writer.Write(" = ");
            WriteIdentifier(moveName, writer);
            writer.Write("(");
            WriteIdentifier(stateName, writer);
            writer.Write(", ");
            WriteIdentifier(cName, writer);
            writer.WriteLine(");");
            writer.Write("if (");
            WriteIdentifier(resultName, writer);
            writer.WriteLine(" == -1)");
            writer.OpenBlock();
            writer.WriteLine("break;");
            writer.CloseBlock();
            Identifier flagName = Identifier.New(Grammar.GetOptionValue("pg.scanner.flag.name", "flag"));
            Identifier flag2Name = Identifier.New(Grammar.GetOptionValue("pg.scanner.flag2.name", "flag2"));
            Identifier flagArrayName = Identifier.New(Grammar.GetOptionValue("pg.scanner.array.flag.name", "_flag"));
            writer.Write("int ");
            WriteIdentifier(flagName, writer);
            writer.Write(" = ");
            WriteIdentifier(flagArrayName, writer);
            writer.Write("[");
            WriteIdentifier(resultName, writer);
            writer.WriteLine(" * 2];");
            writer.Write("if ((");
            WriteIdentifier(flagName, writer);
            writer.WriteLine(" & 0x1) != 0)");
            writer.OpenCurlyBlock();
            writer.Write("int ");
            WriteIdentifier(flag2Name, writer);
            writer.Write(" = ");
            WriteIdentifier(flagArrayName, writer);
            writer.Write("[");
            WriteIdentifier(resultName, writer);
            writer.WriteLine(" * 2 + 1];");
            Identifier atEOLName = Identifier.New(Grammar.GetOptionValue("pg.scanner.ateol.name", "atEOL"));
            Identifier isLineTerminatorName = Identifier.New(Grammar.GetOptionValue("pg.scanner.islineterminator.name", "IsLineTerminator"));
            writer.Write("bool ");
            WriteIdentifier(atEOLName, writer);
            writer.Write(" = ");
            WriteIdentifier(isLineTerminatorName, writer);
            writer.Write("(");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerPeekName, writer);
            writer.Write("()) || ");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerPeekName, writer);
            writer.WriteLine("() == -1;");
            writer.Write("if ((");
            WriteIdentifier(flagName, writer);
            writer.Write(" & 0x2) == 0 || ");
            WriteIdentifier(atEOLName, writer);
            writer.Write(" || (");
            WriteIdentifier(flag2Name, writer);
            writer.WriteLine(" & 0x1) != 0)");
            writer.OpenCurlyBlock();
            WriteIdentifier(tokenEndName, writer);
            writer.Write(" = ");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.WriteLine(";");
            WriteIdentifier(savedName, writer);
            writer.Write(" = ");
            WriteIdentifier(resultName, writer);
            writer.WriteLine(";");
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
            WriteIdentifier(stateName, writer);
            writer.Write(" = ");
            WriteIdentifier(resultName, writer);
            writer.WriteLine(";");
            writer.CloseCurlyBlock();
            Identifier tokenContextName = Identifier.New(Grammar.GetOptionValue("pg.scanner.tokencontext.name", "TokenContext"));
            Identifier sourceContextClassName = Identifier.New(Grammar.GetOptionValue("pg.sourcecontext.classname", "SourceContext"));
            Identifier sourceContextNewName = Identifier.New(Grammar.GetOptionValue("pg.sourcecontext.new.name", "New"));
            string sourceContextNamespace = Grammar.GetOptionValue("pg.sourcecontext.namespace", "ParserGenerator.Input");
            writer.Write("if (");
            WriteIdentifier(savedName, writer);
            writer.WriteLine(" == -1)");
            writer.OpenCurlyBlock();
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.Write(" = ");
            WriteIdentifier(tokenBeginName, writer);
            writer.WriteLine(";");
            WriteIdentifier(tokenContextName, writer);
            writer.Write(" = ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceContextNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceContextClassName, writer);
            writer.Write(".");
            WriteIdentifier(sourceContextNewName, writer);
            writer.Write("(");
            WriteIdentifier(tokenBeginName, writer);
            writer.Write(", ");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.WriteLine(");");
            WriteStartActionSwitch(Automaton, writer);
            writer.CloseCurlyBlock();
            writer.WriteLine("else");
            writer.OpenCurlyBlock();
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.Write(" = ");
            WriteIdentifier(tokenEndName, writer);
            writer.WriteLine(";");
            WriteIdentifier(tokenContextName, writer);
            writer.Write(" = ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceContextNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceContextClassName, writer);
            writer.Write(".");
            WriteIdentifier(sourceContextNewName, writer);
            writer.Write("(");
            WriteIdentifier(tokenBeginName, writer);
            writer.Write(", ");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerLocationName, writer);
            writer.WriteLine(");");
            writer.Write("int ");
            WriteIdentifier(flagName, writer);
            writer.Write(" = ");
            WriteIdentifier(flagArrayName, writer);
            writer.Write("[");
            WriteIdentifier(savedName, writer);
            writer.WriteLine(" * 2];");
            writer.Write("int ");
            WriteIdentifier(flag2Name, writer);
            writer.Write(" = ");
            WriteIdentifier(flagArrayName, writer);
            writer.Write("[");
            WriteIdentifier(savedName, writer);
            writer.WriteLine(" * 2 + 1];");
            writer.Write("bool ");
            WriteIdentifier(atEOLName, writer);
            writer.Write(" = ");
            WriteIdentifier(isLineTerminatorName, writer);
            writer.Write("(");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerPeekName, writer);
            writer.Write("()) || ");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(readerPeekName, writer);
            writer.WriteLine("() == -1;");
            Identifier actionName = Identifier.New(Grammar.GetOptionValue("pg.scanner.action.name", "action"));
            Identifier ruleName = Identifier.New(Grammar.GetOptionValue("pg.scanner.rule.name", "rule"));
            writer.Write("int ");
            WriteIdentifier(actionName, writer);
            writer.Write(" = (");
            WriteIdentifier(flagName, writer);
            writer.Write(" & 0x2) == 0 || ");
            WriteIdentifier(atEOLName, writer);
            writer.Write(" ? ");
            WriteIdentifier(flagName, writer);
            writer.Write(" : ");
            WriteIdentifier(flag2Name, writer);
            writer.WriteLine(";");
            writer.Write("if ((");
            WriteIdentifier(actionName, writer);
            writer.WriteLine(" & 0x4) != 0)");
            writer.OpenBlock();
            writer.Write("return ");
            WriteIdentifier(actionName, writer);
            writer.WriteLine(" >> 4;");
            writer.CloseBlock();
            writer.Write("int ");
            WriteIdentifier(ruleName, writer);
            writer.Write(" = ");
            WriteIdentifier(actionName, writer);
            writer.WriteLine(" >> 4;");
            WriteRuleActionSwitch(Automaton, writer);
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
        }

        public void GenerateMapMethod(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            string mapAccess = Grammar.GetOptionValue("pg.scanner.map.access", "private");
            Identifier mapName = Identifier.New(Grammar.GetOptionValue("pg.scanner.map.name", "Map"));
            Identifier mapIName = Identifier.New(Grammar.GetOptionValue("pg.scanner.map.i.name", "i"));
            Identifier mapJName = Identifier.New(Grammar.GetOptionValue("pg.scanner.map.j.name", "j"));
            Identifier mapKName = Identifier.New(Grammar.GetOptionValue("pg.scanner.map.k.name", "k"));
            Identifier cName = Identifier.New(Grammar.GetOptionValue("pg.scanner.c.name", "c"));
            Identifier fastArrayName = Identifier.New(Grammar.GetOptionValue("pg.scanner.array.fast.name", "_fast"));
            Identifier firstArrayName = Identifier.New(Grammar.GetOptionValue("pg.scanner.array.first.name", "_first"));
            Identifier lastArrayName = Identifier.New(Grammar.GetOptionValue("pg.scanner.array.last.name", "_last"));
            Identifier mapArrayName = Identifier.New(Grammar.GetOptionValue("pg.scanner.array.map.name", "_map"));
            writer.Write(mapAccess);
            if (!mapAccess.IsNullOrEmpty())
                writer.Write(" ");
            writer.Write("static int ");
            WriteIdentifier(mapName, writer);
            writer.Write("(int ");
            WriteIdentifier(cName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            writer.Write("if (0 <= ");
            WriteIdentifier(cName, writer);
            writer.Write(" && ");
            WriteIdentifier(cName, writer);
            writer.Write(" <= ");
            WriteIdentifier(fastArrayName, writer);
            writer.Write(".Length");
            writer.WriteLine(")");
            writer.OpenBlock();
            writer.Write("return ");
            WriteIdentifier(fastArrayName, writer);
            writer.Write("[");
            WriteIdentifier(cName, writer);
            writer.WriteLine("];");
            writer.CloseBlock();
            writer.Write("int ");
            WriteIdentifier(mapIName, writer);
            writer.WriteLine(" = 0;");
            writer.Write("int ");
            WriteIdentifier(mapJName, writer);
            writer.Write(" = ");
            WriteIdentifier(mapArrayName, writer);
            writer.WriteLine(".Length - 1;");
            writer.Write("while (");
            WriteIdentifier(mapIName, writer);
            writer.Write(" <= ");
            WriteIdentifier(mapJName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            writer.Write("int ");
            WriteIdentifier(mapKName, writer);
            writer.Write(" = ");
            WriteIdentifier(mapIName, writer);
            writer.Write(" + (");
            WriteIdentifier(mapJName, writer);
            writer.Write(" - ");
            WriteIdentifier(mapIName, writer);
            writer.WriteLine(" >> 1);");
            writer.Write("if (");
            WriteIdentifier(cName, writer);
            writer.Write(" < ");
            WriteIdentifier(firstArrayName, writer);
            writer.Write("[");
            WriteIdentifier(mapKName, writer);
            writer.WriteLine("])");
            writer.OpenBlock();
            WriteIdentifier(mapJName, writer);
            writer.Write(" = ");
            WriteIdentifier(mapKName, writer);
            writer.WriteLine(" - 1;");
            writer.CloseBlock();
            writer.Write("else if (");
            WriteIdentifier(lastArrayName, writer);
            writer.Write("[");
            WriteIdentifier(mapKName, writer);
            writer.Write("] < ");
            WriteIdentifier(cName, writer);
            writer.WriteLine(")");
            writer.OpenBlock();
            WriteIdentifier(mapIName, writer);
            writer.Write(" = ");
            WriteIdentifier(mapKName, writer);
            writer.WriteLine(" + 1;");
            writer.CloseBlock();
            writer.WriteLine("else");
            writer.OpenBlock();
            writer.Write("return ");
            WriteIdentifier(mapArrayName, writer);
            writer.Write("[");
            WriteIdentifier(mapKName, writer);
            writer.WriteLine("];");
            writer.CloseBlock();
            writer.CloseCurlyBlock();
            writer.WriteLine("return -1;");
            writer.CloseCurlyBlock();
        }

        public void GenerateMoveMethod(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            string moveAccess = Grammar.GetOptionValue("pg.scanner.move.access", "private");
            Identifier moveName = Identifier.New(Grammar.GetOptionValue("pg.scanner.move.name", "Move"));
            Identifier mapName = Identifier.New(Grammar.GetOptionValue("pg.scanner.map.name", "Map"));
            Identifier stateName = Identifier.New(Grammar.GetOptionValue("pg.scanner.state.name", "state"));
            Identifier cName = Identifier.New(Grammar.GetOptionValue("pg.scanner.c.name", "c"));
            Identifier kName = Identifier.New(Grammar.GetOptionValue("pg.scanner.k.name", "k"));
            Identifier baseArrayName = Identifier.New(Grammar.GetOptionValue("pg.scanner.array.base.name", "_sbase"));
            Identifier checkArrayName = Identifier.New(Grammar.GetOptionValue("pg.scanner.array.check.name", "_scheck"));
            Identifier valueArrayName = Identifier.New(Grammar.GetOptionValue("pg.scanner.array.value.name", "_svalue"));
            writer.Write(moveAccess);
            if (!moveAccess.IsNullOrEmpty())
                writer.Write(" ");
            writer.Write("static int ");
            WriteIdentifier(moveName, writer);
            writer.Write("(int ");
            WriteIdentifier(stateName, writer);
            writer.Write(", int ");
            WriteIdentifier(cName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            writer.Write("int ");
            WriteIdentifier(kName, writer);
            writer.Write(" = ");
            WriteIdentifier(mapName, writer);
            writer.Write("(");
            WriteIdentifier(cName, writer);
            writer.WriteLine(");");
            writer.Write("if (");
            WriteIdentifier(kName, writer);
            writer.WriteLine(" == -1)");
            writer.OpenBlock();
            writer.WriteLine("return -1;");
            writer.CloseBlock();
            writer.Write("if (");
            WriteIdentifier(checkArrayName, writer);
            writer.Write("[");
            WriteIdentifier(baseArrayName, writer);
            writer.Write("[");
            WriteIdentifier(stateName, writer);
            writer.Write("] + ");
            WriteIdentifier(kName, writer);
            writer.Write("] == ");
            WriteIdentifier(kName, writer);
            writer.WriteLine(")");
            writer.OpenBlock();
            writer.Write("return ");
            WriteIdentifier(valueArrayName, writer);
            writer.Write("[");
            WriteIdentifier(baseArrayName, writer);
            writer.Write("[");
            WriteIdentifier(stateName, writer);
            writer.Write("] + ");
            WriteIdentifier(kName, writer);
            writer.WriteLine("];");
            writer.CloseBlock();
            writer.WriteLine("else");
            writer.OpenBlock();
            writer.WriteLine("return -1;");
            writer.CloseBlock();
            writer.CloseCurlyBlock();
        }

        public void GenerateIsNonLineTerminatorWhiteSpaceMethod(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            Identifier isNonLineTerminatorWhiteSpaceName = Identifier.New(Grammar.GetOptionValue("pg.scanner.isnonlineterminatorwhitespace.name", "IsNonLineTerminatorWhiteSpace"));
            Identifier cName = Identifier.New(Grammar.GetOptionValue("pg.scanner.c.name", "c"));
            writer.Write("private static bool ");
            WriteIdentifier(isNonLineTerminatorWhiteSpaceName, writer);
            writer.Write("(int ");
            WriteIdentifier(cName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            writer.Write("switch (");
            WriteIdentifier(cName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            writer.WriteLine("case 0x0009: // '\\t'");
            writer.WriteLine("case 0x0020: // ' '");
            writer.WriteLine("case 0x00a0: // '\\u00a0'");
            writer.WriteLine("case 0x1680: // '\\u1680'");
            writer.WriteLine("case 0x2000: // '\\u2000'");
            writer.WriteLine("case 0x2001: // '\\u2001'");
            writer.WriteLine("case 0x2002: // '\\u2002'");
            writer.WriteLine("case 0x2003: // '\\u2003'");
            writer.WriteLine("case 0x2004: // '\\u2004'");
            writer.WriteLine("case 0x2005: // '\\u2005'");
            writer.WriteLine("case 0x2006: // '\\u2006'");
            writer.WriteLine("case 0x2007: // '\\u2007'");
            writer.WriteLine("case 0x2008: // '\\u2008'");
            writer.WriteLine("case 0x2009: // '\\u2009'");
            writer.WriteLine("case 0x200a: // '\\u200a'");
            writer.WriteLine("case 0x202f: // '\\u202f'");
            writer.WriteLine("case 0x205f: // '\\u205f'");
            writer.WriteLine("case 0x3000: // '\\u3000'");
            writer.OpenBlock();
            writer.WriteLine("return true;");
            writer.CloseBlock();
            writer.WriteLine("default:");
            writer.OpenBlock();
            writer.WriteLine("return false;");
            writer.CloseBlock();
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
        }

        public void GenerateIsLineTerminatorMethod(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            Identifier isLineTerminatorName = Identifier.New(Grammar.GetOptionValue("pg.scanner.islineterminator.name", "IsLineTerminator"));
            Identifier cName = Identifier.New(Grammar.GetOptionValue("pg.scanner.c.name", "c"));
            writer.Write("private static bool ");
            WriteIdentifier(isLineTerminatorName, writer);
            writer.Write("(int ");
            WriteIdentifier(cName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            writer.Write("switch (");
            WriteIdentifier(cName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            writer.WriteLine("case 0x000a: // '\\n'");
            writer.WriteLine("case 0x000b: // '\\v'");
            writer.WriteLine("case 0x000c: // '\\f'");
            writer.WriteLine("case 0x000d: // '\\r'");
            writer.WriteLine("case 0x0085: // '\\u0085'");
            writer.WriteLine("case 0x2028: // '\\u2028'");
            writer.WriteLine("case 0x2029: // '\\u2029'");
            writer.OpenBlock();
            writer.WriteLine("return true;");
            writer.CloseBlock();
            writer.WriteLine("default:");
            writer.OpenBlock();
            writer.WriteLine("return false;");
            writer.CloseBlock();
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
        }

        public void GenerateScanNextTokenMethod(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            bool isIndented = Grammar.GetOptionValue("pg.indented", true);
            Identifier scanNextTokenName = Identifier.New(Grammar.GetOptionValue("pg.scanner.scannexttoken.name", "ScanNextToken"));
            Identifier prescanNextTokenCallbackName = Identifier.New(Grammar.GetOptionValue("pg.scanner.prescannexttokencallback.name", "PrescanNextTokenCallback"));
            writer.Write("private int ");
            WriteIdentifier(isIndented ? prescanNextTokenCallbackName : scanNextTokenName, writer);
            writer.WriteLine("()");
            writer.OpenCurlyBlock();
            GenerateScanWhileLoop(writer);
            writer.CloseCurlyBlock();
        }

        public void GenerateGetNextTokenMethod(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            bool isIndented = Grammar.GetOptionValue("pg.indented", true);
            string getNextTokenAccess = Grammar.GetOptionValue("pg.scanner.getnexttoken.access", "public");
            Identifier getNextTokenName = Identifier.New(Grammar.GetOptionValue("pg.scanner.getnexttoken.name", "GetNextToken"));
            Identifier scanNextTokenName = Identifier.New(Grammar.GetOptionValue("pg.scanner.scannexttoken.name", "ScanNextToken"));
            Identifier prescanNextTokenName = Identifier.New(Grammar.GetOptionValue("pg.scanner.prescannexttoken.name", "PrescanNextToken"));
            Identifier tokenContextName = Identifier.New(Grammar.GetOptionValue("pg.scanner.tokencontext.name", "TokenContext"));
            Identifier tokenValueName = Identifier.New(Grammar.GetOptionValue("pg.scanner.tokenvalue.name", "TokenValue"));
            Identifier tagName = Identifier.New(Grammar.GetOptionValue("pg.scanner.tag.name", "tag"));
            Identifier tokenBeginName = Identifier.New(Grammar.GetOptionValue("pg.scanner.tokenbegin.name", "TokenBegin"));
            Identifier readerName = Identifier.New(Grammar.GetOptionValue("pg.reader.name", "Reader"));
            Identifier locationName = Identifier.New(Grammar.GetOptionValue("pg.reader.location.name", "Location"));
            Identifier sourceContextClassName = Identifier.New(Grammar.GetOptionValue("pg.sourcecontext.classname", "SourceContext"));
            Identifier sourceContextNewName = Identifier.New(Grammar.GetOptionValue("pg.sourcecontext.new.name", "New"));
            string sourceContextNamespace = Grammar.GetOptionValue("pg.sourcecontext.namespace", "ParserGenerator.Input");
            writer.Write(getNextTokenAccess);
            if (!getNextTokenAccess.IsNullOrEmpty())
                writer.Write(" ");
            writer.Write("int ");
            WriteIdentifier(getNextTokenName, writer);
            writer.WriteLine("()");
            writer.OpenCurlyBlock();
            WriteIdentifier(tokenContextName, writer);
            writer.WriteLine(" = null;");
            WriteIdentifier(tokenValueName, writer);
            writer.WriteLine(" = null;");
            writer.Write("int ");
            WriteIdentifier(tagName, writer);
            writer.Write(" = ");
            WriteIdentifier(isIndented ? prescanNextTokenName : scanNextTokenName, writer);
            writer.WriteLine("();");
            writer.Write("if (");
            WriteIdentifier(tokenContextName, writer);
            writer.WriteLine(" == null)");
            writer.OpenBlock();
            WriteIdentifier(tokenContextName, writer);
            writer.Write(" = ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceContextNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceContextClassName, writer);
            writer.Write(".");
            WriteIdentifier(sourceContextNewName, writer);
            writer.Write("(");
            WriteIdentifier(tokenBeginName, writer);
            writer.Write(", ");
            WriteIdentifier(readerName, writer);
            writer.Write(".");
            WriteIdentifier(locationName, writer);
            writer.WriteLine(");");
            writer.CloseBlock();
            writer.Write("return ");
            WriteIdentifier(tagName, writer);
            writer.WriteLine(";");
            writer.CloseCurlyBlock();
        }

        public void GenerateKeywordCaseList(ReadOnlyCollection<GrammarTerminalSymbol> symbols, bool caseOnly, IndentedWriter writer)
        {
            if (symbols == null)
                throw new ArgumentNullException(nameof(symbols));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            foreach (GrammarTerminalSymbol sym in symbols)
            {
                if (sym.Value == null)
                    continue;
                writer.Write("case ");
                WriteStringLiteral(sym.Value, writer);
                writer.Write(":");
                writer.WriteLine();
                if (!caseOnly)
                {
                    writer.OpenBlock();
                    writer.Write("return ");
                    WriteIntegerLiteral(sym.Number, writer);
                    writer.WriteLine(";");
                    writer.CloseBlock();
                }
            }
        }

        public void GenerateMapIdentifierMethod(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            string mapIdentifierAccess = Grammar.GetOptionValue("pg.scanner.mapidentifier.access", "public");
            Identifier mapIdentifierNameName = Identifier.New(Grammar.GetOptionValue("pg.scanner.mapidentifier.name.name", "name"));
            Identifier mapIdentifierName = Identifier.New(Grammar.GetOptionValue("pg.scanner.mapidentifier.name", "MapIdentifier"));
            writer.Write(mapIdentifierAccess);
            if (!mapIdentifierAccess.IsNullOrEmpty())
                writer.Write(" ");
            writer.Write("static int ");
            WriteIdentifier(mapIdentifierName, writer);
            writer.Write("(");
            writer.Write("string ");
            WriteIdentifier(mapIdentifierNameName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            writer.Write("if (");
            WriteIdentifier(mapIdentifierNameName, writer);
            writer.WriteLine(" == null)");
            writer.OpenBlock();
            writer.Write("throw new ");
            if (UseFullyQualifiedNamespace)
                writer.Write("global::System.");
            writer.Write("ArgumentNullException(nameof(");
            WriteIdentifier(mapIdentifierNameName, writer);
            writer.WriteLine("));");
            writer.CloseBlock();
            writer.Write("switch (");
            WriteIdentifier(mapIdentifierNameName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            GenerateKeywordCaseList(Automaton.Tokens.Keywords, false, writer);
            GenerateKeywordCaseList(Automaton.Tokens.ReservedWords, false, writer);
            GenerateKeywordCaseList(Automaton.Tokens.ContextualWords, false, writer);
            writer.WriteLine("default:");
            writer.OpenBlock();
            GrammarTerminalSymbol id = Automaton.Tokens.LookupNamed("Identifier");
            writer.Write("return ");
            WriteIntegerLiteral(id.Number, writer);
            writer.WriteLine(";");
            writer.CloseBlock();
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
        }

        public void GenerateIsKeywordMethod(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            string isKeywordAccess = Grammar.GetOptionValue("pg.scanner.iskeyword.access", "public");
            Identifier isKeywordNameName = Identifier.New(Grammar.GetOptionValue("pg.scanner.iskeyword.name.name", "name"));
            Identifier isKeywordName = Identifier.New(Grammar.GetOptionValue("pg.scanner.iskeyword.name", "IsKeyword"));
            writer.Write(isKeywordAccess);
            if (!isKeywordAccess.IsNullOrEmpty())
                writer.Write(" ");
            writer.Write("static bool ");
            WriteIdentifier(isKeywordName, writer);
            writer.Write("(");
            writer.Write("string ");
            WriteIdentifier(isKeywordNameName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            writer.Write("if (");
            WriteIdentifier(isKeywordNameName, writer);
            writer.WriteLine(" == null)");
            writer.OpenBlock();
            writer.Write("throw new ");
            if (UseFullyQualifiedNamespace)
                writer.Write("global::System.");
            writer.Write("ArgumentNullException(nameof(");
            WriteIdentifier(isKeywordNameName, writer);
            writer.WriteLine("));");
            writer.CloseBlock();
            writer.Write("switch (");
            WriteIdentifier(isKeywordNameName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            if (Automaton.Tokens.Keywords.Count > 0 || Automaton.Tokens.ReservedWords.Count > 0)
            {
                GenerateKeywordCaseList(Automaton.Tokens.Keywords, true, writer);
                GenerateKeywordCaseList(Automaton.Tokens.ReservedWords, true, writer);
                writer.OpenBlock();
                writer.WriteLine("return true;");
                writer.CloseBlock();
            }
            writer.WriteLine("default:");
            writer.OpenBlock();
            writer.WriteLine("return false;");
            writer.CloseBlock();
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
        }

        public void GenerateTestMethod(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            Identifier testName = Identifier.New(Grammar.GetOptionValue("pg.scanner.test.name", "Test"));
            Identifier scannerClassName = Identifier.New(Grammar.GetOptionValue("pg.scanner.classname", "Tokenizer"));
            string scannerNamespace = Grammar.GetOptionValue("pg.scanner.namespace", "ParserGenerator.LexicalAnalysis");
            GenerateTestMethod(writer, testName, scannerClassName, scannerNamespace);
        }

        public void GenerateTestMethod(IndentedWriter writer, Identifier methodName, Identifier scannerClassName, string scannerNamespace)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            if (methodName == null)
                throw new ArgumentNullException(nameof(methodName));
            if (scannerClassName == null)
                throw new ArgumentNullException(nameof(scannerClassName));
            if (scannerNamespace == null)
                throw new ArgumentNullException(nameof(scannerNamespace));
            string testAccess = Grammar.GetOptionValue("pg.scanner.test.access", "public");
            Identifier testScannerName = Identifier.New(Grammar.GetOptionValue("pg.scanner.test.scanner.name", "tokenizer"));
            Identifier testEName = Identifier.New(Grammar.GetOptionValue("pg.scanner.test.e.name", "e"));
            Identifier tagName = Identifier.New(Grammar.GetOptionValue("pg.scanner.tag.name", "tag"));
            Identifier getNextTokenName = Identifier.New(Grammar.GetOptionValue("pg.scanner.getnexttoken.name", "GetNextToken"));
            Identifier tokenContextName = Identifier.New(Grammar.GetOptionValue("pg.scanner.tokencontext.name", "TokenContext"));
            Identifier sourceContextBeginName = Identifier.New(Grammar.GetOptionValue("pg.sourcecontext.begin.name", "Begin"));
            Identifier tokensArrayName = Identifier.New(Grammar.GetOptionValue("pg.scanner.array.tokens.name", "_tokens"));
            Identifier exceptionExtensionsClassName = Identifier.New(Grammar.GetOptionValue("pg.exceptionextensions.classname", "ExceptionExtensions"));
            string exceptionExtensionsNamespace = Grammar.GetOptionValue("pg.exceptionextensions.namespace", "ParserGenerator.Extensions");
            Identifier exceptionExtensionsPrintName = Identifier.New(Grammar.GetOptionValue("pg.exceptionextensions.print.name", "Print"));
            Identifier sourceReaderClassName = Identifier.New(Grammar.GetOptionValue("pg.sourcereader.classname", "SourceReader"));
            string sourceReaderNamespace = Grammar.GetOptionValue("pg.sourcereader.namespace", "ParserGenerator.Input");
            Identifier sourceReaderFromConsoleName = Identifier.New(Grammar.GetOptionValue("pg.sourcereader.fromconsole.name", "FromConsole"));
            Identifier readerPeekName = Identifier.New(Grammar.GetOptionValue("pg.reader.peek.name", "Peek"));
            Identifier testReaderName = Identifier.New(Grammar.GetOptionValue("pg.scanner.test.reader.name", "reader"));
            string endMarkerName = Grammar.GetOptionValue("pg.endmarker.name", "None");
            GrammarTerminalSymbol endMarker = Automaton.Tokens.LookupNamed(endMarkerName);
            writer.Write(testAccess);
            if (!testAccess.IsNullOrEmpty())
                writer.Write(" ");
            writer.Write("static void ");
            WriteIdentifier(methodName, writer);
            writer.WriteLine("()");
            writer.OpenCurlyBlock();
            writer.WriteLine("while (true)");
            writer.OpenCurlyBlock();
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceReaderNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceReaderClassName, writer);
            writer.Write(" ");
            WriteIdentifier(testReaderName, writer);
            writer.Write(" = ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(sourceReaderNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(sourceReaderClassName, writer);
            writer.Write(".");
            WriteIdentifier(sourceReaderFromConsoleName, writer);
            writer.WriteLine("();");
            writer.Write("if (");
            WriteIdentifier(testReaderName, writer);
            writer.Write(".");
            WriteIdentifier(readerPeekName, writer);
            writer.WriteLine("() == -1)");
            writer.OpenBlock();
            writer.WriteLine("break;");
            writer.CloseBlock();
            writer.WriteLine("try");
            writer.OpenCurlyBlock();
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(scannerNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(scannerClassName, writer);
            writer.Write(" ");
            WriteIdentifier(testScannerName, writer);
            writer.Write(" = new ");
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(scannerNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(scannerClassName, writer);
            writer.Write("(");
            WriteIdentifier(testReaderName, writer);
            writer.WriteLine(");");
            writer.WriteLine("while (true)");
            writer.OpenCurlyBlock();
            writer.Write("int ");
            WriteIdentifier(tagName, writer);
            writer.Write(" = ");
            WriteIdentifier(testScannerName, writer);
            writer.Write(".");
            WriteIdentifier(getNextTokenName, writer);
            writer.WriteLine("();");
            if (UseFullyQualifiedNamespace)
                writer.Write("global::System.");
            writer.Write("Console.WriteLine(\"{0}: {1} {2}\", ");
            WriteIdentifier(testScannerName, writer);
            writer.Write(".");
            WriteIdentifier(tokenContextName, writer);
            writer.Write(".");
            WriteIdentifier(sourceContextBeginName, writer);
            writer.Write(", ");
            WriteIdentifier(tokensArrayName, writer);
            writer.Write("[");
            WriteIdentifier(tagName, writer);
            writer.Write("], ");
            WriteIdentifier(testScannerName, writer);
            writer.Write(".");
            WriteIdentifier(tokenContextName, writer);
            writer.WriteLine(");");
            writer.Write("if (");
            WriteIdentifier(tagName, writer);
            writer.WriteLine(" == {0})", endMarker.Number);
            writer.OpenBlock();
            writer.WriteLine("break;");
            writer.CloseBlock();
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
            writer.Write("catch (");
            if (UseFullyQualifiedNamespace)
                writer.Write("global::System.");
            writer.Write("Exception ");
            WriteIdentifier(testEName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            if (UseFullyQualifiedNamespace)
            {
                writer.Write("global::");
                WriteDottedName(exceptionExtensionsNamespace, writer);
                writer.Write(".");
            }
            WriteIdentifier(exceptionExtensionsClassName, writer);
            writer.Write(".");
            WriteIdentifier(exceptionExtensionsPrintName, writer);
            writer.Write("(");
            WriteIdentifier(testEName, writer);
            writer.WriteLine(");");
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
        }
    }
}
